package panda.natalia.gothread.suspend.instrument;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.BasicInterpreter;
import org.objectweb.asm.tree.analysis.BasicValue;

import static panda.natalia.gothread.suspend.instrument.GoThreadMethodInstrumentor.ASMAPI;

/**
 * a simple framework of java fiber
 *
 * @Author: scpanda2007
 * @Date: 2019/11/28 18:39
 *
 *  用于收集 objects 和 arrays
 */
class TypeInterpreter extends BasicInterpreter {

    private final MethodDatabase db;

    public TypeInterpreter(MethodDatabase db){
        super(ASMAPI);
        this.db = db;
    }

    /**
     * 默认 Type.OBJECT 和 Type.ARRAY 是返回了一个 固定的reference Value
     *      case 9: // Type.ARRAY
     *      case 10: // Type.OBJECT
     *          return BasicValue.REFERENCE_VALUE; // REFERENCE_VALUE = new BasicValue(Type.getObjectType("java/lang/Object"));
     * @param type
     * @return
     */
    @Override
    public BasicValue newValue(Type type){
        if(type == null) return BasicValue.UNINITIALIZED_VALUE;

        if(type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) return new BasicValue(type);

        return super.newValue(type);
    }

    @Override
    public BasicValue newOperation(AbstractInsnNode insnNode) throws AnalyzerException {
        if(insnNode.getOpcode() == Opcodes.NEW)
            return new NewValue(Type.getObjectType(((TypeInsnNode)insnNode).desc), false, insnNode);
        return super.newOperation(insnNode);
    }

    @Override
    public BasicValue copyOperation(AbstractInsnNode insn, BasicValue value) throws AnalyzerException {
        if (insn.getOpcode() == Opcodes.DUP){
            if(value instanceof NewValue){
                NewValue newValue = (NewValue) value;
                if(!newValue.isDupped)
                    return new NewValue(newValue.getType(), true, insn);
            }
        }
        return super.copyOperation(insn, value);
    }

    @Override
    public BasicValue binaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2) throws AnalyzerException {
        if (insn.getOpcode() == Opcodes.AALOAD){
            final Type t1 = value1.getType();
            if(t1 == null)
                throw new AnalyzerException(insn, "AALOAD needs a first paramter");

            final Type resultType = Type.getType(t1.getDescriptor().substring(1));
            return new BasicValue(resultType);
        }
        return super.binaryOperation(insn, value1, value2);
    }

    /**
     *  覆写了原方法 !value1.equals(value2) ? BasicValue.UNINITIALIZED_VALUE : value1;
     * @param v
     * @param w
     * @return
     */
    @Override
    public BasicValue merge(BasicValue v, BasicValue w) {
        if (!v.equals(w)){
            if(v.isReference() && w.isReference()){
                int dimensions = 0;
                Type typeV = v.getType();
                Type typeW = w.getType();

                final String internalV = typeV.getInternalName();
                final String internalW = typeW.getInternalName();

                if("null".equals(internalV)) return w;
                if("null".equals(internalW)) return v;

                if (typeV.getSort() != typeW.getSort()) {

                    return BasicValue.UNINITIALIZED_VALUE;
                }
                if(typeW.getSort() != Type.ARRAY){
                    dimensions = typeV.getDimensions();
                    if(dimensions != typeW.getDimensions()){

                        return BasicValue.UNINITIALIZED_VALUE;
                    }
                    typeV = typeV.getElementType();
                    typeW = typeW.getElementType();

                    if(typeV.getSort() != Type.OBJECT || typeW.getSort() != Type.OBJECT){

                        return BasicValue.UNINITIALIZED_VALUE;
                    }
                }

                String superClass = db.getCommonSuperClass(internalV, internalW); //
                if(superClass == null) {
                    if(db.isException(internalW)){
                        return w;
                    }
                    if(db.isException(internalV)){
                        return v;
                    }
                    superClass = "java/lang/Object";
                }

                String typeDescriptor = makeTypeDescriptor(superClass, dimensions);
                return new BasicValue(Type.getType(typeDescriptor));
            }
            return BasicValue.UNINITIALIZED_VALUE;
        }
        return v;
    }

    /**
     * 这个方法实际上 用char数组节省了一下性能
     * 实际用 String 拼接会更容易懂
     * 创建一个 object 对象数组的 描述符,
     * @param className object 对象的jvm类名
     * @param dimensions 数组的维度
     * @return
     */
    private static String makeTypeDescriptor(String className, int dimensions){
        final int len = className.length();
        final char[] tmp = new char[len + 2 + dimensions];
        for(int i = 0; i < dimensions; i++){
            tmp[i] = '[';
        }

        tmp[dimensions] = 'L';
        className.getChars(0, len, tmp, dimensions + 1);
        tmp[dimensions + 1 + len] = ';';
        return new String(tmp);
    }
}
